/*
    Nios-sim - one simple NIOSII simulator only for personal interest and fun.
    Copyright (C) 2010  chysun2000@gmail.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include "public.h"
#include "niosii.h"
#include "instruction.h"

static uint32_t wrctl(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	cpu->ctrl_regs[instr->n] = cpu->gp_regs[instr->a];

	return PC_INC_NORMAL;
}


static uint32_t eret(struct NIOS_CPU * cpu, uint32_t code)
{
	cpu->ctrl_regs[status] = cpu->ctrl_regs[estatus];
	cpu->pc = cpu->gp_regs[ea];

	return PC_INC_BY_INSTR;
}

static uint32_t roli(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	uint32_t shift_bit = (instr->n) ;
	uint32_t value = cpu->gp_regs[instr->a];
	uint32_t temp = 0;
	
	temp = value >> (32 - shift_bit);
	cpu->gp_regs[instr->c] = value << shift_bit;
	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->c] | temp;
	return PC_INC_NORMAL;
}

static uint32_t rol(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	uint32_t shift_bit = cpu->gp_regs[instr->b] & 0x1F;
	uint32_t temp = (cpu->gp_regs[instr->a]) >> (32 - shift_bit);
	
	cpu->gp_regs[instr->c] = (cpu->gp_regs[instr->a] << shift_bit) | temp;
	return PC_INC_NORMAL;
}

static uint32_t flushp(struct NIOS_CPU * cpu, uint32_t code)
{
	return PC_INC_NORMAL;
}

static uint32_t ret(struct NIOS_CPU * cpu, uint32_t code)
{
	cpu->pc = cpu->gp_regs[ra];

	return PC_INC_BY_INSTR;
}

static uint32_t nor(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	cpu->gp_regs[instr->c] = ~(cpu->gp_regs[instr->a] | cpu->gp_regs[instr->b]);
	return PC_INC_NORMAL;
}

static uint32_t mulxuu(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	uint64_t temp = 0;
	
	temp = (uint32_t)cpu->gp_regs[instr->a];
	temp = temp * (uint32_t)cpu->gp_regs[instr->b];
	cpu->gp_regs[instr->c] = ((uint32_t)(temp >> 32)) & (0xFFFFFFFF);
	return PC_INC_NORMAL;
}

static uint32_t cmpge(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	if ((int32_t)(cpu->gp_regs[instr->a]) >= (int32_t)(cpu->gp_regs[instr->b])){
		cpu->gp_regs[instr->c] = 1;
	}
	else {
		cpu->gp_regs[instr->c] = 0;
	}
	return PC_INC_NORMAL;
}

static uint32_t bret(struct NIOS_CPU * cpu, uint32_t code)
{

	cpu->ctrl_regs[status] = cpu->ctrl_regs[bstatus];
	cpu->pc = cpu->gp_regs[ba];
	return PC_INC_BY_INSTR;
}

static uint32_t ror(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	uint32_t shift_bit = cpu->gp_regs[instr->b] & 0x1F;
	uint32_t temp = cpu->gp_regs[instr->a] << (32 - shift_bit);

	cpu->gp_regs[instr->c] = (cpu->gp_regs[instr->a] >> shift_bit) | temp;
	return PC_INC_NORMAL;
}

static uint32_t flushi(struct NIOS_CPU * cpu, uint32_t code)
{
	return PC_INC_NORMAL;
}

static uint32_t jmp(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	cpu->pc = cpu->gp_regs[instr->a];
	return PC_INC_BY_INSTR;
}

static uint32_t and(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] & cpu->gp_regs[instr->b];
	return PC_INC_NORMAL;
}

static uint32_t cmplt(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	if ((int32_t)(cpu->gp_regs[instr->a]) < (int32_t)(cpu->gp_regs[instr->b])){
		cpu->gp_regs[instr->c] = 1;
	}
	else{
		cpu->gp_regs[instr->c] = 0;
	}
	return PC_INC_NORMAL;
}

static uint32_t slli(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] << instr->n;
	return PC_INC_NORMAL;
}

static uint32_t sll(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] << (cpu->gp_regs[instr->b] & 0x1F);
	return PC_INC_NORMAL;
}

static uint32_t wrprs(struct NIOS_CPU * cpu, uint32_t code)
{
	return PC_INC_NORMAL;
}

static uint32_t or(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] | cpu->gp_regs[instr->b];
	return PC_INC_NORMAL;
}

static uint32_t mulxsu(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	int64_t a = (int32_t)(cpu->gp_regs[instr->a]);
    int64_t temp = 0;
	
	temp = a * cpu->gp_regs[instr->b];
	cpu->gp_regs[instr->c] = (uint32_t)((temp >> 32));
	return PC_INC_NORMAL;
}

static uint32_t cmpne(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	if (cpu->gp_regs[instr->a] != cpu->gp_regs[instr->b]){
		cpu->gp_regs[instr->c] = 1;
	}
	else{
		cpu->gp_regs[instr->c] = 0;
	}
	return PC_INC_NORMAL;
}
static uint32_t srli(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] >> (uint32_t)(instr->n);
	return PC_INC_NORMAL;
}
static uint32_t srl(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] >> (cpu->gp_regs[instr->b] & 0x1F);
	return PC_INC_NORMAL;
}

static uint32_t nextpc(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	cpu->gp_regs[instr->c] = cpu->pc + 4;
	return PC_INC_NORMAL;
}

static uint32_t callr(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	cpu->gp_regs[ra] = cpu->pc + 4;
	cpu->pc = cpu->gp_regs[instr->a];
	return PC_INC_BY_INSTR;
}

static uint32_t xor(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	uint32_t a = cpu->gp_regs[instr->a];
	uint32_t b = cpu->gp_regs[instr->b];

	cpu->gp_regs[instr->c] = a ^ b;
	return PC_INC_NORMAL;
}

static uint32_t mulxss(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	int32_t a = (int32_t)cpu->gp_regs[instr->a];
	int32_t b = (int32_t)cpu->gp_regs[instr->b];
	uint64_t temp = 0;
	
	temp = a * b;
	cpu->gp_regs[instr->c] = (uint32_t) (temp >> 32);
	return PC_INC_NORMAL;
}

static uint32_t cmpeq(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	if(cpu->gp_regs[instr->a] == cpu->gp_regs[instr->b]){
		cpu->gp_regs[instr->c] = 1;
	}
	else{
		cpu->gp_regs[instr->c] = 0;
	}
	return PC_INC_NORMAL;
}

static uint32_t divu(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	if (cpu->gp_regs[instr->b] != 0){
		cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] / cpu->gp_regs[instr->b];
	}
	return PC_INC_NORMAL;
}

static uint32_t div(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	int32_t a = (int32_t)cpu->gp_regs[instr->a];
	int32_t b = (int32_t)cpu->gp_regs[instr->b];

	if (b != 0){
		cpu->gp_regs[instr->c] = a / b;
	}
	return PC_INC_NORMAL;
}

static uint32_t rdctl(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	cpu->gp_regs[instr->c] = cpu->ctrl_regs[instr->n];
	return PC_INC_NORMAL;
}

static uint32_t mul(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	cpu->gp_regs[instr->c] = (cpu->gp_regs[instr->a] * cpu->gp_regs[instr->b]) & 0xFFFFFFFF;
	return PC_INC_NORMAL;
}

static uint32_t cmpgeu(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	if (cpu->gp_regs[instr->a] >= cpu->gp_regs[instr->b]){
		cpu->gp_regs[instr->c] = 1;
	}
	else{
		cpu->gp_regs[instr->c] = 0;
	}
	return PC_INC_NORMAL;
}

static uint32_t initi(struct NIOS_CPU * cpu, uint32_t code)
{
	return PC_INC_NORMAL;
}

static uint32_t trap(struct NIOS_CPU * cpu, uint32_t code)
{
	cpu->ctrl_regs[estatus] = cpu->ctrl_regs[status];
	cpu->ctrl_regs[status] = cpu->ctrl_regs[status] & 0xFFFFFFFC;
	cpu->gp_regs[ea] = cpu->pc + 4;
	cpu->pc = EXCEPTION_HANDLER_ADDR;
	return PC_INC_BY_INSTR;
}

static uint32_t cmpltu(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	
	if (cpu->gp_regs[instr->a] < cpu->gp_regs[instr->b]){
		cpu->gp_regs[instr->c] = 1;
	}
	else {
		cpu->gp_regs[instr->c] = 0;
	}
	return PC_INC_NORMAL;
}

static uint32_t add(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] + cpu->gp_regs[instr->b];
	return PC_INC_NORMAL;
}

static uint32_t instr_break(struct NIOS_CPU * cpu, uint32_t code)
{
#ifdef NIOS_SIM_SUPPORT_BREAK
	cpu->ctrl_regs[bstatus] = cpu->ctrl_regs[status];
	cpu->ctrl_regs[status] = cpu->ctrl_regs[status] & 0xFFFFFFFC;
	cpu->gp_regs[ba] = cpu->pc + 4;
	cpu->pc = BREAK_HANDLER_ADDRESS;
	return PC_INC_BY_INSTR;
#else
	return PC_INC_NORMAL;
#endif
}

static uint32_t instr_sync(struct NIOS_CPU * cpu, uint32_t code)
{
	return PC_INC_NORMAL;
}

static uint32_t sub(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	cpu->gp_regs[instr->c] = cpu->gp_regs[instr->a] - cpu->gp_regs[instr->b];
	return PC_INC_NORMAL;
}

static uint32_t srai(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;

	int32_t a = (int32_t)cpu->gp_regs[instr->a];
	cpu->gp_regs[instr->c] = a >> (uint32_t)(instr->n);
	return PC_INC_NORMAL;
}

static uint32_t sra(struct NIOS_CPU * cpu, uint32_t code)
{
	def_r_type_code;
	int32_t a = (int32_t) cpu->gp_regs[instr->a];
	
	cpu->gp_regs[instr->c] = a >> (uint32_t)(cpu->gp_regs[instr->b] & 0x1F);
	return PC_INC_NORMAL;
}

#define R_TYPE_HANDLER_COUNT (0x40)

struct r_type_handler r_type_handlers[R_TYPE_HANDLER_COUNT] = {
	[ERET] = handler_item(eret),
	[ROLI] = handler_item(roli),
	[ROL] = handler_item(rol),
	[FLUSHP] = handler_item(flushp),
	[RET] = handler_item(ret),
	[NOR] = handler_item(nor),
	[MULXUU] = handler_item(mulxuu),
	[CMPGE] = handler_item(cmpge),
	[BRET] = handler_item(bret),
	[ROR] = handler_item(ror),
	[FLUSHI] = handler_item(flushi),
	[JMP] = handler_item(jmp),
	[AND] = handler_item(and),
	[CMPLT] = handler_item(cmplt),
	[SLLI] = handler_item(slli),
	[SLL] = handler_item(sll),
	[WRPRS] = handler_item(wrprs),
	[OR] = handler_item(or),
	[MULXSU] = handler_item(mulxsu),
	[CMPNE] = handler_item(cmpne),
	[SRLI] = handler_item(srli),
	[SRL] = handler_item(srl),
	[NEXTPC] = handler_item(nextpc),
	[CALLR] = handler_item(callr),
	[XOR] = handler_item(xor),
	[MULXSS] = handler_item(mulxss),
	[CMPEQ] = handler_item(cmpeq),
	[DIVU] = handler_item(divu),
	[DIV] = handler_item(div),
	[RDCTL] = handler_item(rdctl),
	[MUL] = handler_item(mul),
	[CMPGEU] = handler_item(cmpgeu),
	[INITI] = handler_item(initi),
	[TRAP] = handler_item(trap),
	[WRCTL] = handler_item(wrctl),
	[CMPLTU] = handler_item(cmpltu),
	[ADD] = handler_item(add),
	[BREAK] = handler_item(instr_break),
	[SYNC] = handler_item(instr_sync),
	[SUB] = handler_item(sub),
	[SRAI] = handler_item(srai),
	[SRA] = handler_item(sra)
};



